package com.cee.auditlog.handler;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerUpdateStatement;
import com.alibaba.druid.sql.dialect.sqlserver.parser.SQLServerStatementParser;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.toolkit.Constants;

import com.cee.auditlog.bean.AuditLog;
import com.cee.auditlog.process.AuditLogProcess;
import com.cee.dao.MyBatisMapper;
import com.cee.util.BeanUtil;
import com.cee.util.JsonUtil;
import com.cee.util.SpringContextUtil;
import com.cee.util.StringUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SQLServerUpdateHandler implements SQLHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(SQLServerUpdateHandler.class);
    
    private String sql;
    
    private String tableName;
    
    private List<String> updateColumnList = new ArrayList<>();
    
    private List<Map<String, Object>> resultListBeforeUpdate;
    
    private SQLStatement sqlStatement;
    
    private List<Object> whereColumnValueList;

    private String selectSQl;
    private String rowId;
    private Object object = null;

    @SuppressWarnings("rawtypes")
    @Override
    public void preHandle(MappedStatement mappedStatement, Object parameterObject) {
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);

        this.sql = boundSql.getSql();
//        logger.info("sql: {}", sql);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (sql.toUpperCase().indexOf("WHERE") == -1) {
            logger.info("skip the sql[{}], it doesn't contain where clause.", sql);
            return;
        }

        this.sqlStatement = new SQLServerStatementParser(sql).parseUpdateStatement();
        SQLTableSource sqlTableSource = ((SQLServerUpdateStatement)sqlStatement).getTableSource();

        if (sqlTableSource != null) {
            
            if (sqlStatement instanceof SQLServerUpdateStatement) {
                SQLServerUpdateStatement updateStatement = (SQLServerUpdateStatement)sqlStatement;
                
                tableName = sqlTableSource.toString();
                
                List<SQLUpdateSetItem> updateSetItems = updateStatement.getItems();
                SQLExpr where = updateStatement.getWhere();

                if (StringUtils.isNotBlank(tableName)) {
                    
                    for (SQLUpdateSetItem sqlUpdateSetItem : updateSetItems) {
                        
                        String aliasAndColumn[] = SQLUtils.toSQLServerString(sqlUpdateSetItem.getColumn()).split(",");
                        String column = aliasAndColumn[0].trim();
                        updateColumnList.add(column);
                    }
                    
                    SQLServerSelectQueryBlock selectQueryBlock = new SQLServerSelectQueryBlock();
                    selectQueryBlock.setFrom(sqlTableSource);
                    selectQueryBlock.setWhere(where);
                    
                    for (String column : updateColumnList) {
                        selectQueryBlock.getSelectList().add(new SQLSelectItem(SQLUtils.toSQLExpr(String.format("%s", column))));
                    }
                    whereColumnValueList = new ArrayList<>();
                    String[] split = where.toString().split(",");
                    for (String whereClause : split) {
                        String[] split2 = StringUtils.split(whereClause, "=");
                        String columnName = split2[0];
                        String fieldName = StringUtil.underlineToCamel2(columnName.toLowerCase());
                        
                        if (parameterObject instanceof Map) {
                            for (ParameterMapping mapping : parameterMappings) {
                                
                                if (mapping.getProperty().endsWith(fieldName)) {
                                    
                                    try {

                                        MapperMethod.ParamMap paramMap = ((MapperMethod.ParamMap)parameterObject);
                                        if (paramMap.containsKey(Constants.ENTITY)) {
                                            object = paramMap.get(Constants.ENTITY);
                                        } else {
                                            
                                        }
                                        Object value = BeanUtil.getPropertyValue(object, fieldName);
                                        whereColumnValueList.add(value);
                                    }
                                    catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                    if (whereColumnValueList.size() == 0) {
                        logger.warn("could not detect the primary key in where columns");
                        return;
                    }
                    logger.info("start-> getTableData from {}", tableName);
                    selectSQl = SQLUtils.toSQLServerString(selectQueryBlock).toUpperCase();

                    resultListBeforeUpdate =
                        getTablesData(tableName, updateColumnList, whereColumnValueList);
                    logger.info("end-> getTableData from {}", tableName);
                }
            }
        }
    }

    @Override
    public void setRowId(String rowId) {
        this.rowId = rowId;
    }

    @SuppressWarnings("unchecked")
    public void postHandle(AuditLogProcess myBatisAuditTrailProcess) {
        if (CollectionUtils.isEmpty(whereColumnValueList)) {
            logger.warn("postHandle-> whereColumnValueList.size is 0");
            return;
        }
        logger.info("whereColumnValueList: {}", whereColumnValueList.size());
        if (CollectionUtils.isEmpty(whereColumnValueList)) {
            return;
        }
        Map<String, List<AuditLog>> auditLogsOfRowId = new CaseInsensitiveMap();
        
        int rowCount = resultListBeforeUpdate.size();
        int columnCount = updateColumnList.size();

        List<Map<String, Object>> resultListAfterUpdate = getTablesData(tableName, updateColumnList, whereColumnValueList);
        logger
            .info("rowsBeforeUpdate.size(): {}, rowsAfterUpdateListMap.size(): {}", resultListBeforeUpdate.size(), resultListAfterUpdate.size());
        if (CollectionUtils.isNotEmpty(resultListBeforeUpdate) && CollectionUtils.isNotEmpty(resultListAfterUpdate)
            && resultListBeforeUpdate.size() == resultListAfterUpdate.size()) {

            for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {

                String pkValue = whereColumnValueList.get(0).toString();
                List<AuditLog> columnLogsOfRow = new ArrayList<>();
                auditLogsOfRowId.put(pkValue, columnLogsOfRow);

                Map<String, Object> resultBeforeUpdate = resultListBeforeUpdate.get(rowIndex);
                Map<String, Object> resultAfterUpdate = resultListAfterUpdate.get(rowIndex);

                for (Map.Entry<String, Object> entry : resultBeforeUpdate.entrySet()) {
                    String columnName = entry.getKey();
                    Object oldValue = entry.getValue();

                    Object newValue = resultAfterUpdate.get(columnName);

                    if (ObjectUtils.notEqual(oldValue, newValue)) {
//                        logger.info("{} -> oldValue: {}, newValue: {}", columnName, oldValue, newValue);

                        AuditLog auditLog = new AuditLog();
                        auditLog.setTableName(tableName);
                        auditLog.setActionCode(SqlCommandType.UPDATE.toString());
                        auditLog.setRowId(pkValue);
                        auditLog.setColumnName(columnName);
                        auditLog.setOldValue(BeanUtil.format(oldValue));
                        auditLog.setNewValue(BeanUtil.format(newValue));
                        columnLogsOfRow.add(auditLog);
                    }
                }
            }
            
            logger.info("auditLogListMap: " + auditLogsOfRowId.size());
            if (myBatisAuditTrailProcess != null) {
                myBatisAuditTrailProcess.process(tableName, SqlCommandType.UPDATE.toString(), auditLogsOfRowId);
            }
        }
    }
    
    private List<Map<String, Object>> getTablesData(
        String currentTableName, List<String> tableColumnNames, List<Object> whereColumnValueList) {

        List<Map<String, Object>> resultList = new ArrayList<>();
        MyBatisMapper myBatisMapper = SpringContextUtil.getBean(MyBatisMapper.class);
        String propName = BeanUtil.getPropName(object, TableId.class);
        if (propName != null) {

            String sql = String.format("%s ", selectSQl.replace("?", String.format("%s", whereColumnValueList.get(0))));
            logger.info("select sql: {}", sql);
            Map<String, Object> reuslt = myBatisMapper.selectOne(sql);
            logger.info("result：{}", JsonUtil.format(reuslt));
            resultList.add(reuslt);
        }

        return resultList;
    }
    
}
